﻿using System;
using System.Collections.Generic;
using System.Text;
using Netbits.Common.Collections;
using Netbits.Common.Threading;
using Netbits.TelnetServer.Enums;
using Netbits.TelnetServer.Parameters;

namespace Netbits.TelnetServer
{
	public class NetbitsTelnetClientSession : TerminalTelnetClientSession
	{
		#region Private Variables

		NetbitsTelnetServer _Server;
		int _AuthState;

		StringBuilder _Command;
		int _CommandPos;
		int _CursorStartPos;
		
		ITelnetAuthentication _User;
		string _Username;
		string _CommandLineText;

		ListExtended<string> _History;
		int _HistoryIndex = -1;

		Folder _BaseFolder;
		Folder _SelectedFolder;
		bool _IsInString;
		bool _Disposed;

		Lock _LockPrint;

		#endregion

		#region Constructor

		public NetbitsTelnetClientSession( System.Net.Sockets.TcpClient Client_ ) : base( Client_ )
		{
			base.Echoed = true;
			base.SuppressGoAhead = true;
			base.WindowSize = true;
			base.LineMode_Modus = EnumTelnetLinemodeModes.TrapSIG;

			_LockPrint = new Lock( true );
			_History = new ListExtended<string>();
			_Command = new StringBuilder();
			_BaseFolder = new Folder( null, "main" );
			_SelectedFolder = _BaseFolder;
		}

		#endregion

		#region Public Properties

		public int MaxHistoryCommandCount
		{
			get
			{
				return _History.MaxItems;
			}
			set
			{
				_History.MaxItems = value;
			}
		}

		public new bool SuppressGoAhead
		{
			get
			{
				return base.SuppressGoAhead;
			}
			set
			{
				throw new InvalidOperationException( "you can't change property suppress-go-ahead. fixed set to true!" );
			}
		}

		public new bool Echoed
		{
			get
			{
				return base.Echoed;
			}
			set
			{
				throw new InvalidOperationException( "you can't change property echoed. fixed set to true!" );
			}
		}

		public new bool WindowSize
		{
			get
			{
				return base.WindowSize;
			}
			set
			{
				throw new InvalidOperationException( "you can't change property window-size. fixed set to true! " );
			}
		}

		#endregion

		#region Public Functions

		/// <summary>
		/// Returns the instance of a new PrintAttributeList class. 
		/// This class locks Printing after call function "Print"
		/// </summary>
		/// <returns>new PrintAttributeList instance.</returns>
		public PrintAttributeList GetPrintAttributeList()
		{
			return new PrintAttributeList( this );
		}

		#endregion

		#region Private Functions

		bool _IsAuthenticated()
		{
			if( _Server.Authenticate == null )
				return true;

			if( _AuthState > 1 )
				return true;

			return false;
		}

		void _PrintWelcome()
		{
			base.EraseDisplay();
			if( !string.IsNullOrEmpty( _Server.WelcomeMessage ) )
				Send( _Server.WelcomeMessage );
		}

		void _CreateCommandLineText()
		{
			// Authentication needed
			if( _Server.Authenticate != null )
			{
				if( _AuthState == 0 )
				{
					_CommandLineText = string.Format( "{0}{1}{2}", GetCursorPos1(), GetEraseLine(), "username: " );
					_CursorStartPos = 11;
					return;
				}
				if( _AuthState == 1 )
				{
					_CommandLineText = string.Format( "{0}{1}{2}", GetCursorPos1(), GetEraseLine(), "password: " );
					_CursorStartPos = 11;
					return;
				}
			}

			_CursorStartPos = 1;
			_CommandLineText = string.Format( "{0}{1}", GetCursorPos1(), GetEraseLine() );
			_CommandLineText += "[";
			++_CursorStartPos;
			if( _Server.Authenticate != null )
			{
				_CommandLineText += GetAttribute( EnumTelnetColors.Cyan, EnumTelnetColors.Nothing, EnumTelnetAttributes.Bright );
				_CommandLineText += _User.Username;
				_CursorStartPos += _User.Username.Length;
				_CommandLineText += GetResetColorAttributes();
				_CommandLineText += "@";
				++_CursorStartPos;
			}
			_CommandLineText += GetAttribute( EnumTelnetColors.Green, EnumTelnetColors.Nothing, EnumTelnetAttributes.Dim );
			_CommandLineText += _Server.Name;
			_CursorStartPos += _Server.Name.Length;
			_CommandLineText += GetResetColorAttributes();
			_CommandLineText += "] ";
			_CursorStartPos += 2;

			string foldername = _SelectedFolder.GetFullFoldername();
			_CommandLineText += foldername;
			_CursorStartPos += foldername.Length;

			_CommandLineText += "> ";
			_CursorStartPos += 2;
		}

		void _PrintCommandLine()
		{
			if( _Server.Authenticate != null )
			{
				if( _AuthState == 1 ) // password
					base.Send( _CommandLineText + new StringBuilder().Append( '*', _Command.Length ).ToString(), false );
				else
					base.Send( _CommandLineText + _Command, false );
			}
			else
			{
				base.Send( _CommandLineText + _Command, false );
			}

			CursorPosition( _CursorStartPos + _CommandPos );
		}

		void _HandleKeyCR()
		{
			if( _AuthState < 2 && _Server.Authenticate != null )
			{
				if( _AuthState == 0 )
				{
					_Username = _Command.ToString();
					_AuthState = 1;
					_Command.Clear();
					_CommandPos = 0;
					_CreateCommandLineText();
					_PrintCommandLine();
					return;
				}
				else if( _AuthState == 1 )
				{
					string password = _Command.ToString();
					_User = _Server.Authenticate( _Username, password );
					if( _User == null )
					{
						base.Send( "", true );
						base.Send( "authentication failed! disconnect." );
						Disconnect();
						return;
					}

					_AuthState = 2;
					_Command.Clear();
					_CommandPos = 0;
					_CreateCommandLineText();
					_PrintCommandLine();
					return;
				}
			}

			string command = _Command.ToString();
			base.Send( string.Empty, true );

			if( !string.IsNullOrWhiteSpace( command ) )
			{
				if( command == ".." )
				{
					if( _SelectedFolder != _BaseFolder )
					{
						_SelectedFolder = _SelectedFolder.Parent;
						_CreateCommandLineText();
					}
				}
				else
				{
					if( !_HandleExecute( command ) )
						return;

					string lastcommand = string.Empty;
					if( _History.Count > 0 )
						lastcommand = _History[_History.Count - 1];

					if( lastcommand != command )
						_History.Add( command );

					_HistoryIndex = -1;
				}
			}

			_Command.Clear();
			_CommandPos = 0;
			_PrintCommandLine();
		}

		bool _HandleExecute( string Command_ )
		{
			Folder f = null;
			Command c = null;
			ParameterBase p = null;
			string s = string.Empty;
			bool i = false;

			string cmd = _Command.ToString();
			if( !cmd.EndsWith( " " ) )
				cmd += " ";

			try
			{
				_ParseCommand( cmd, ref f, ref c, ref p, ref s, ref i );

				if( i )
				{
					base.Send( Environment.NewLine, false );
					if( _CommandPos == _Command.Length )
					{
						_Command.Append( Environment.NewLine );
						++_CommandPos;
						base.Send( Environment.NewLine, false );
					}
					else
					{
						_Command.Insert( _CommandPos, Environment.NewLine );
						++_CommandPos;
						_PrintCommandLine();
					}
					return false;
				}

				if( !string.IsNullOrEmpty( s ) )
				{
					string msg = base.GetAttribute( EnumTelnetColors.Red, EnumTelnetColors.Nothing, EnumTelnetAttributes.Bright );
					msg += s;
					msg += base.GetResetColorAttributes();

					base.Send( "can't execute command '{0}'. unknown string '{1}'", cmd, msg );
					return true;
				}

				if( c == null )
				{
					_SelectedFolder = f;
					_CreateCommandLineText();
					_PrintCommandLine();
					return true;
				}

				foreach( ParameterBase pb in c.Parameters )
				{
					if( pb.MustSet && !pb.IsSet )
					{
						string msg = base.GetAttribute( EnumTelnetColors.Red, EnumTelnetColors.Nothing, EnumTelnetAttributes.Bright );
						msg += string.Format( "parameter '{0}' not set!", pb.Name );
						msg += base.GetResetColorAttributes();

						base.Send( "can't execute command '{0}'. {1}", cmd, msg );
						return true;
					}
				}

				c.Execute( this, c );
				_CreateCommandLineText();
				_PrintCommandLine();
				return true;
			}
			catch( Exception Ex_ )
			{
				string msg = base.GetAttribute( EnumTelnetColors.Red, EnumTelnetColors.Nothing, EnumTelnetAttributes.Bright );
				msg += Ex_.Message;
				msg += base.GetResetColorAttributes();

				base.Send( "can't execute command '{0}'. {1}", cmd, msg );
				return true;
			}
		}

		void _HandleCompletion()
		{
			if( !_IsAuthenticated() )
				return;

			Folder f = null;
			Command c = null;
			ParameterBase p = null;
			string s = string.Empty;
			bool i = false;

			_ParseCommand( _Command.ToString(), ref f, ref c, ref p, ref s, ref i );

			List<string> opportunities = new List<string>();
			if( c == null )
			{
				IList<Folder> folders = null;
				if( f == null )
					folders = _SelectedFolder.FindFolder( s );
				else
					folders = f.FindFolder( s );

				foreach( Folder fx in folders )
				{
					if( !fx.Invisible )
						opportunities.Add( fx.Name );
				}

				IList<Command> commands = null;
				if( f == null )
					commands = _SelectedFolder.FindCommand( s );
				else
					commands = f.FindCommand( s );

				foreach( Command cd in commands )
				{
					if( !cd.Invisible )
						opportunities.Add( cd.Name );
				}
			}
			else
			{
				IList<ParameterBase> parameters = c.FindParameter( s );
				foreach( ParameterBase pb in parameters )
				{
					if( !pb.Invisible )
						opportunities.Add( pb.Name );
				}
			}

			if( opportunities.Count > 0 )
			{
				if( opportunities.Count == 1 )
				{
					if( c == null )
						_Command.Append( opportunities[0].Substring( s.Length ) + " " );
					else
						_Command.Append( opportunities[0].Substring( s.Length ) + "=" );
				}
				else
				{
					string same = _GetMaxSame( opportunities );
					_Command.Append( same.Substring( s.Length ) );
				}
				_CommandPos = _Command.Length;
				_PrintCommandLine();
			}
		}

		string _GetMaxSame( IList<string> Values_ )
		{
			StringBuilder max = new StringBuilder();
			int minsize = int.MaxValue;
			foreach( string s in Values_ )
			{
				if( s.Length < minsize )
					minsize = s.Length;
			}

			for( int i = 0; i < minsize; i++ )
			{
				char c = char.MinValue;
				foreach( string s in Values_ )
				{
					if( c == char.MinValue )
					{
						c = s[i];
						continue;
					}

					if( c != s[i] )
						return max.ToString();
				}
				max.Append( c );
			}

			return string.Empty;
		}

		void _ParseCommand( string Command_, ref Folder SelectedFolder_, ref Command SelectedCommand_, ref ParameterBase SelectedParameter_, ref string UnknownString_, ref bool InString_ )
		{
			Folder startFolder = _SelectedFolder;
			bool isFirstChar = true;
			StringBuilder sb = new StringBuilder();
			InString_ = false;

			foreach( char c in Command_ )
			{
				#region Handle first char if char is '/'

				if( isFirstChar )
				{
					isFirstChar = false;
					if( c == '/' ) // Start on base-folder.
					{
						startFolder = _BaseFolder;
						continue;
					}
				}

				#endregion

				#region Handle space (split folder/command/parameter)

				if( !InString_ && c == ' ' )
				{
					string cmd = sb.ToString();
					sb.Clear();

					if( SelectedFolder_ == null )
					{
						IList<Folder> f = startFolder.FindFolder( cmd );
						IList<Command> cd = startFolder.FindCommand( cmd );
						if( cd.Count == 1 )
						{
							SelectedFolder_ = startFolder;
							SelectedCommand_ = cd[0];
							continue;
						}

						if( f.Count == 1 )
						{
							SelectedFolder_ = f[0];
							continue;
						}

						UnknownString_ = cmd;
						return;
					}

					if( SelectedCommand_ == null )
					{
						IList<Folder> f = SelectedFolder_.FindFolder( cmd );
						IList<Command> cd = SelectedFolder_.FindCommand( cmd );
						if( cd.Count == 1 )
						{
							SelectedCommand_ = cd[0];
							continue;
						}

						if( f.Count == 1 )
						{
							SelectedFolder_ = f[0];
							continue;
						}

						UnknownString_ = cmd;
						return;
					}

					if( SelectedParameter_ == null )
					{
						IList<ParameterBase> pb = SelectedCommand_.FindParameter( cmd );
						if( pb.Count == 1 )
						{
							SelectedParameter_ = pb[0];
							continue;
						}

						UnknownString_ = cmd;
						return;
					}

					SelectedParameter_.SetValue( cmd );
					SelectedParameter_ = null;
					continue;
				}

				#endregion

				#region Handle = (parameter-value)

				if( !InString_ && c == '=' )
				{
					string cmd = sb.ToString();
					sb.Clear();

					if( SelectedCommand_ == null )
					{
						UnknownString_ = cmd;
						return;
					}

					IList<ParameterBase> parameters = SelectedCommand_.FindParameter( cmd );
					if( parameters.Count != 1 )
					{
						UnknownString_ = cmd;
						return;
					}

					SelectedParameter_ = parameters[0];
					SelectedParameter_.IsSet = true;
					continue;
				}

				#endregion

				#region Handle " (string literal)

				if( SelectedParameter_ != null && c == '"' )
				{
					if( !InString_ )
					{
						if( sb.Length == 0 )
							InString_ = true;
					}
					else
					{
						SelectedParameter_.SetValue( sb.ToString() );
						SelectedParameter_ = null;
						sb.Clear();
						InString_ = false;
					}
				}

				#endregion

				sb.Append( c );
			}

			if( sb.Length > 0 )
				UnknownString_ = sb.ToString();
		}

		#endregion

		#region Internal Properties

		/// <summary>
		/// Returns the print-lock instance.
		/// </summary>
		internal Lock PrintLock
		{
			get
			{
				return _LockPrint;
			}
		}

		#endregion

		#region Internal Functions

		internal void SetServer( NetbitsTelnetServer Server_ )
		{
			_Server = Server_;
			_BaseFolder = _Server.Folder.Clone() as Folder;
			_SelectedFolder = _BaseFolder;
		}

		#endregion

		#region Overrides

		public override void Send( byte[] Buffer_, int IndexOf_, int Size_ )
		{
			_LockPrint.EnterWriteLock();
			try
			{
				base.Send( Buffer_, IndexOf_, Size_ );
			}
			finally
			{
				_LockPrint.ExitWriteLock();
			}
		}

		protected sealed override void _HandleAscii( byte Key_ )
		{
			if( Key_ == '?' && _IsAuthenticated() )
			{
				Folder folder = null;
				Command command = null;
				ParameterBase parameter = null;
				string unknown = string.Empty;
				bool instring = false;

				_ParseCommand( _Command.ToString(), ref folder, ref command, ref parameter, ref unknown, ref instring );

				if( !instring )
				{
					base.Send( string.Empty );
					if( folder == null )
					{
						_SelectedFolder.PrintInfo( this, unknown );
						_PrintCommandLine();
						return;
					}

					if( command == null )
					{
						folder.PrintInfo( this, unknown );
						_PrintCommandLine();
						return;
					}

					if( parameter == null )
					{
						command.PrintInfo( this, unknown );
						_PrintCommandLine();
						return;
					}

					parameter.PrintInfo( this, unknown );
					_PrintCommandLine();
					return;
				}
			}

			if( _CommandPos == _Command.Length )
			{
				_Command.Append( (char)Key_ );
				++_CommandPos;

				if( _AuthState == 1 ) // password
					base.Send( "*", false );
				else
					base.Send( new byte[] { Key_ }, 0, 1 );
			}
			else
			{
				_Command.Insert( _CommandPos, (char)Key_ );
				++_CommandPos;
				_PrintCommandLine();
			}
		}

		protected sealed override void _HandleKey( EnumTelnetKeys Key_ )
		{
			switch( Key_ )
			{
				case EnumTelnetKeys.CR:
					_HandleKeyCR();
					break;
				case EnumTelnetKeys.KEY_POS1:
					_CommandPos = 0;
					base.CursorPosition( _CursorStartPos + _CommandPos );
					break;
				case EnumTelnetKeys.KEY_END:
					_CommandPos = _Command.Length;
					base.CursorPosition( _CursorStartPos + _CommandPos );
					break;
				case EnumTelnetKeys.KEY_LEFT:
					if( _CommandPos == 0 )
						return;
					--_CommandPos;
					base.CursorPosition( _CursorStartPos + _CommandPos );
					break;
				case EnumTelnetKeys.KEY_RIGHT:
					if( _CommandPos == _Command.Length )
						return;
					++_CommandPos;
					base.CursorPosition( _CursorStartPos + _CommandPos );
					break;
				case EnumTelnetKeys.BS:
				case EnumTelnetKeys.BS2:
					if( _CommandPos == 0 )
						return;
					_Command.Remove( _CommandPos - 1, 1 );
					--_CommandPos;
					_PrintCommandLine();
					break;
				case EnumTelnetKeys.KEY_DELETE:
					if( _CommandPos == _Command.Length )
						return;
					_Command.Remove( _CommandPos, 1 );
					_PrintCommandLine();
					break;
				case EnumTelnetKeys.KEY_UP:
					if( _HistoryIndex == -1 )
						_HistoryIndex = _History.Count - 1;
					else
						--_HistoryIndex;

					if( _HistoryIndex == -1 )
					{
						_HistoryIndex = 0;
						break;
					}

					_Command.Clear();
					_Command.Append( _History[_HistoryIndex] );
					_CommandPos = _Command.Length;
					_PrintCommandLine();					
					break;
				case EnumTelnetKeys.KEY_DOWN:
					if( _HistoryIndex == -1 )
						return;

					++_HistoryIndex;
					if( _HistoryIndex == _History.Count )
					{
						_Command.Clear();
						_CommandPos = 0;
						_PrintCommandLine();
						_HistoryIndex = -1;
					}
					else
					{
						_Command.Clear();
						_Command.Append( _History[_HistoryIndex] );
						_CommandPos = _Command.Length;
						_PrintCommandLine();
					}
					break;
				case EnumTelnetKeys.TAB:
					_HandleCompletion();
					break;
			}
		}

		protected sealed override void _HandleInit()
		{
			_PrintWelcome();
			_CreateCommandLineText();
			_PrintCommandLine();
		}

		protected sealed override void _HandleWindowSize( int Width_, int Height_ )
		{
		}

		protected override void Dispose( bool Disposing_ )
		{
			if( !_Disposed )
			{
				if( Disposing_ )
				{
					_Server = null;
					_User = null;
					_BaseFolder = null;
					_SelectedFolder = null;

					if( _History != null )
					{
						_History.Clear();
						_History = null;
					}

					if( _LockPrint != null )
					{
						_LockPrint.Dispose();
						_LockPrint = null;
					}
				}

				_Disposed = true;
			}
			
			base.Dispose( Disposing_ );
		}

		#endregion
	}
}
